package p.ithorns.example.task;

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;
import p.ithorns.framework.common.time.TimeUtil;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Optional;
import java.util.function.Consumer;

/**
 * TaskDetector
 * 任务检测
 *
 * @author HtL
 * @date 2024/5/9 20:19
 * @since 1.0.0
 */
@Slf4j
public class TaskDetector {

    /**
     * 检测是否到达执行时间，并执行
     *
     * @param param    -检测时间到期参数
     * @param callback -回调 参数dt为当前执行时间
     */
    public static void detectionExpiry4Creation(
            TaskDetectionParam param, Consumer<TaskDetectionParam> callback
    ) {
        LocalDateTime now = param.getCurrent();
        // 周期单位 是 时/次
        if (CycleUnit.MINUTE.getCode().equals(param.getCycleUnit())
                // 设置了每日最大任务数
                && null != param.getDailyTasks()) {
            // 次数未达到上限
            Integer execCount = Optional.ofNullable(param.getExecCount()).orElse(0);
            if (hasExpired(param)) {
                // 检查初始时间 置 0
                LocalDateTime nowInit = param.getInit()
                        .withYear(now.getYear())
                        .withMonth(now.getMonthValue())
                        .withDayOfMonth(now.getDayOfMonth());
                if (isApproximate(now, nowInit)) {
                    log.info("走初始路径： MINUTES.between({}, {})", now, nowInit);
                    param.setExecCount(1);
                    param.setLast(now);
                    log.info("第[{}]次执行", param.getExecCount());
                    // 异步执行任务
                    callback.accept(param);
                    return;
                }

                if (execCount < param.getDailyTasks()) {
                    log.info("走正常路径： MINUTES.between({}, {})", now, nowInit);
                    param.setExecCount(execCount + 1);
                    param.setLast(now);
                    log.info("第[{}]次执行", param.getExecCount());
                    // 异步执行任务
                    callback.accept(param);
                }
            }
            return;
        }
        // 其他情况，判断是否达到执行时间，重置次数，并执行
        if (hasExpired(param)) {
            log.info("其他情况");
            param.setExecCount(1);
            param.setLast(now);
            log.info("第[{}]次执行", param.getExecCount());
            // 异步执行任务
            callback.accept(param);
        }
    }

    private static LocalDateTime minus1Cycle(TaskDetectionParam param) {
        CycleUnit cycleUnit = CycleUnit.get(param.getCycleUnit());
        LocalDateTime initTime = param.getInit();
        if (null != cycleUnit) {
            Integer inspectCycle = param.getCycle();
            switch (cycleUnit) {
                case HOUR:
                    return initTime.minusHours(inspectCycle);
                case DAY:
                    return initTime.minusDays(inspectCycle);
                case MONTH:
                    return initTime.minusMonths(inspectCycle);
                case YEAR:
                    return initTime.minusYears(inspectCycle);
            }
        }
        return initTime;
    }

    /**
     * 是否到期
     */
    private static boolean hasExpired(TaskDetectionParam param) {
        // 初始执行时间
        LocalDateTime init = param.getInit();
        // 最近一次执行时间
        LocalDateTime last = param.getLast();
        // 当前时间
        LocalDateTime now = param.getCurrent();
        // 周期单位
        String unitCode = param.getCycleUnit();
        // 执行周期
        int cycle = param.getCycle();
        // 第一次执行
        if (null == last || isEqual(now, last)) {
            // 初次执行
            return isAfterAndEqual(now, init);
        } else {
            CycleUnit unit = CycleUnit.get(unitCode);
            Assert.notNull(unit, "不合法的周期单位:  " + unitCode);
            LocalDateTime next = unit.plusCycle(last, cycle);
            return isAfterAndEqual(now, next);
        }
    }

    private static boolean isEqual(LocalDateTime dt1, LocalDateTime dt2) {
        return accurate2Minute(dt1).isEqual(accurate2Minute(dt2));
    }


    /**
     * 判断 dt1 >= dt2
     */
    private static boolean isAfterAndEqual(LocalDateTime dt1, LocalDateTime dt2) {
        return !accurate2Minute(dt1).isBefore(accurate2Minute(dt2));
    }

    private static boolean isAfter(LocalDateTime dt1, LocalDateTime dt2) {
        return accurate2Minute(dt1).isAfter(accurate2Minute(dt2));
    }

    /**
     * 判断 d1 == d2
     */
    private static boolean isSameDay(LocalDateTime d1, LocalDateTime d2) {
        if (null == d1 || null == d2) {
            return true;
        }

        return accurate2Day(d1).isEqual(accurate2Day(d2));
    }

    /**
     * 精确时间到分钟
     */
    private static LocalDateTime accurate2Minute(LocalDateTime dt) {
        Assert.notNull(dt, "参数为null");
        return dt.truncatedTo(ChronoUnit.MINUTES);
    }

    private static LocalDateTime accurate2Day(LocalDateTime dt) {
        Assert.notNull(dt, "参数为null");
        return dt.truncatedTo(ChronoUnit.DAYS);
    }

    /**
     * 是否到期
     */
    private boolean hasExpired(LocalDateTime init, LocalDateTime last, LocalDateTime now,
                               String cycleUnit, int cycle) {
        if (null == last) {
            // 初次执行
            log.info("初次检测: {}", init);
            Assert.notNull(init, "初始时间为null.");
            return isAfterAndEqual(now, init);
        } else {
            Assert.notNull(last, "最后执行时间未设置");
            CycleUnit cu = CycleUnit.get(cycleUnit);
            LocalDateTime next = cu.plusCycle(last, cycle);
            log.info("周期检测: {}", next);
            return isAfterAndEqual(now, next);
        }
    }


    /**
     * 检测任务是否过期
     *
     * @param param    -检测参数
     * @param callback -回调
     */
    public static void detection4TaskExpiration(
            TaskExpiryParam param, Consumer<Boolean> callback
    ) {
        String unitCode = param.getCycleUnit();
        CycleUnit cycleUnit = CycleUnit.get(unitCode);
        LocalDateTime taskTime = cycleUnit.plusCycle(param.getCreateTime(), param.getSchedule());
        if (isAfterAndEqual(param.getCurrent(), taskTime)) {
            callback.accept(true);
        }
    }

    // 近似
    private static boolean isApproximate(LocalDateTime t1, LocalDateTime t2) {
        int t1m = t1.getMinute();
        int t2m = t2.getMinute();
        if (t1m == t2m) {
            long abs = Math.abs(ChronoUnit.SECONDS.between(t1, t2));
            log.info("SECONDS.between({}, {}) = {}", t1, t2, abs);
            return abs < 30L;
        }
        return false;
    }

    private static boolean isApproximateHour(LocalDateTime t1, LocalDateTime t2) {
        int t1h = t1.getHour();
        int t2h = t2.getHour();
        if (t1h == t2h) {
            // 同小时的情况下，时间不超过5分钟的差值
            long abs = Math.abs(ChronoUnit.MINUTES.between(t1, t2));
            log.info("MINUTES.between({}, {}) = {}", t1, t2, abs);
            return abs < 5L;
        }
        return false;
    }

    public static void main(String[] args) {
        // MINUTES.between(2024-06-14T13:38:00.016, 2024-06-14T13:38:38.730)
        LocalDateTime t1 = TimeUtil.asLocalDateTime("2024-06-14 13:38:00.016");
        LocalDateTime t2 = TimeUtil.asLocalDateTime("2024-06-14 13:38:38.730");
        long b1 = ChronoUnit.SECONDS.between(t1, t2);
        System.out.println(b1);

        System.out.println(isApproximate(t1, t2));

        // MINUTES.between(2024-06-14T13:39:00.011, 2024-06-14T13:38:38.730)
        LocalDateTime t3 = TimeUtil.asLocalDateTime("2024-06-14 13:39:00.011");
        LocalDateTime t4 = TimeUtil.asLocalDateTime("2024-06-14 13:38:38.730");
        System.out.println(isApproximate(t3, t4));

        long minutes = Duration.between(t3, t4).toMinutes();
        System.out.println(minutes);

        LocalDateTime t5 = TimeUtil.asLocalDateTime("2024-06-14 13:38:38.730");
        LocalDateTime t6 = TimeUtil.asLocalDateTime("2024-06-14 14:38:00.011");
        System.out.println(isApproximateHour(t5, t6));

        System.exit(0);
    }
}